package pl.edu.agh.uddiProxy.dao.hibernate;

import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityNotFoundException;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.security.AuthenticationCredentialsNotFoundException;
import org.springframework.security.context.SecurityContextHolder;

import pl.edu.agh.uddiProxy.dao.DAOHelper;
import pl.edu.agh.uddiProxy.dao.DynamicParameterDAO;
import pl.edu.agh.uddiProxy.query.DistanceFunction;
import pl.edu.agh.uddiProxy.types.Authority;
import pl.edu.agh.uddiProxy.types.ParameterType;
import pl.edu.agh.uddiProxy.types.ServiceDynamicParameters;
import pl.edu.agh.uddiProxy.types.User;

public class DynamicParameterHibernateDAO extends HibernateDaoSupport implements DynamicParameterDAO {
	private static final String QUERY_PREFIX = "SELECT s.tModelKey, s.id FROM serviceDynamicParameters s";
	private static final String QUERY_SUFIX = " GROUP BY s.tModelKey, s.id";
	
	private static String queryJoins;
	private static Logger logger = Logger.getLogger(DynamicParameterHibernateDAO.class);
	
	public String getQueryJoins() {
		if (queryJoins == null) {
			StringBuffer buffer = new StringBuffer();
			int i = 1;
			for (String name : DAOHelper.getParametersMap().getParametersMap().keySet()) {
				buffer.append(" JOIN serviceDynamicParameters_map sm");
				buffer.append(i);
				buffer.append(" ON s.id = sm");
				buffer.append(i);
				buffer.append(".param_id JOIN parameterValue p");
				buffer.append(i);
				buffer.append(" ON (p");
				buffer.append(i);
				buffer.append(".id = sm");
				buffer.append(i);
				buffer.append(".map_id AND p");
				buffer.append(i);
				buffer.append(".name ='");
				buffer.append(name);
				buffer.append("')");
				i++;
			}
			buffer.append(" WHERE ");
			queryJoins = buffer.toString();
		}
		return queryJoins;
	}

	public void create(ServiceDynamicParameters dynamicParameter) throws DataAccessException {
		getHibernateTemplate().save(dynamicParameter);
		getHibernateTemplate().flush();
	}

	public void delete(ServiceDynamicParameters dynamicParameter) {
		getHibernateTemplate().delete(dynamicParameter);
		getHibernateTemplate().flush();
	}

	public boolean exists(String tModelKey) {
		return getByTModel(tModelKey) != null;
	}

	public synchronized List<ServiceDynamicParameters> getAll() {
		List<ServiceDynamicParameters> result = (List<ServiceDynamicParameters>) getHibernateTemplate().execute(
				new HibernateCallback() {
			public Object doInHibernate(final Session session)
					throws SQLException {
				SQLQuery sqlQuery = session.createSQLQuery("SELECT * FROM serviceDynamicParameters");
				sqlQuery.addEntity(ServiceDynamicParameters.class);
				return sqlQuery.list();
			}
		});

		return result;
	}

	public List<Long> getAllIds() {
		List<ServiceDynamicParameters> entityList = getAll();
		List<Long> idList = new LinkedList<Long>();

		if (entityList == null) {
			return null;
		}

		for (ServiceDynamicParameters e : entityList) {
			idList.add(e.getId());
		}

		return idList;
	}

	public void update(ServiceDynamicParameters dynamicParameter) {
		User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		if (!SecurityContextHolder.getContext().getAuthentication().isAuthenticated() || !user.isUser()) {
			throw new AuthenticationCredentialsNotFoundException(Authority.getAuthority(Authority.USER).getAuthority());
		}
		if (!user.isAdministrator()) {
			user = DAOHelper.getUserDAO().getById(user.getId());
			List<ServiceDynamicParameters> list = user.getServiceList();
			
			if (!list.contains(dynamicParameter)) {
				list.add(dynamicParameter);
				DAOHelper.getUserDAO().update(user);

				getHibernateTemplate().update(dynamicParameter);
				getHibernateTemplate().flush();
			}
		} else {
			getHibernateTemplate().update(dynamicParameter);
			getHibernateTemplate().flush();
		}
	}

	public boolean exists(Long id) {
		return getById(id) != null;
	}

	public synchronized ServiceDynamicParameters getById(final Long id) {
		if (id == null) {
			throw new EntityNotFoundException("ID is null");
		}

		ServiceDynamicParameters result = (ServiceDynamicParameters) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(final Session session)
					throws SQLException {
				Criteria criteria = session.createCriteria(ServiceDynamicParameters.class);
				criteria.add(Restrictions.eq("id", id));
				return criteria.uniqueResult();
			}
		});


		return result;
	}

	public synchronized ServiceDynamicParameters getByTModel(final String tModelKey) {
		if (tModelKey == null) {
			throw new EntityNotFoundException("ID is null");
		}

		ServiceDynamicParameters result = (ServiceDynamicParameters) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(final Session session)
					throws SQLException {
				Criteria criteria = session.createCriteria(ServiceDynamicParameters.class);
				criteria.add(Restrictions.eq("tModelKey", tModelKey));
				return criteria.uniqueResult();
			}
		});


		return result;
	}

	public List<String> getTModels(final String query) {
		return getTModels(query, null);
		
	}

	public List<String> getTModels(String query,
			List<String> models) {
		List<ServiceDynamicParameters> l = getDynamicParameters(query, models);
		List<String> list = new LinkedList<String>();
		
		for (ServiceDynamicParameters dynamicParameters : l) {
			list.add(dynamicParameters.getTModelKey());
		}
		
		return list;
	}

	public List<ServiceDynamicParameters> getDynamicParameters(String query) {
		return getDynamicParameters(query, null);
	}

	public synchronized List<ServiceDynamicParameters> getDynamicParameters(String query,
			final List<String> models) {
		if (query == null) {
			return null;
		}

		final StringBuffer buffer = new StringBuffer();
		buffer.append(QUERY_PREFIX);
		buffer.append(getQueryJoins());
		String transform = transformQuery(query);
		buffer.append(transform);
		if (models != null && models.size() > 0) {
			if (transform.length() > 0) {
				buffer.append(" AND ");	
			}
			buffer.append(" s.tModelKey IN (");
			for (int i = 0; i < models.size(); i++) { 
				buffer.append("? ");
				if (i < models.size() - 1) {
					buffer.append(", ");	
				}
			}
			buffer.append(")");
		}
		buffer.append(QUERY_SUFIX);
		
		logger.info(buffer.toString());
		
		List<ServiceDynamicParameters> list = (List<ServiceDynamicParameters>) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(final Session session)
					throws SQLException {
				SQLQuery sqlQuery = session.createSQLQuery(buffer.toString());
				sqlQuery.addEntity(ServiceDynamicParameters.class);
				
				if (models != null && models.size() > 0) {
					for (int i = 0; i < models.size(); i++) { 
						sqlQuery.setString(i, models.get(i));
					}
				}
				
				return sqlQuery.list();
			}
		});

		return sort(list, query);
	}
	
	public String transformQuery(String query) {
		if (query == null || query.trim().length() == 0) {
			return "";
		}
		
		Map<String, ParameterType> map = DAOHelper.getParametersMap().getParametersMap();
		
		query = "(" + query + ")";
		query = query.replaceAll("(?i)and", ")and(");		//?i: case insensitive match
		query = query.replaceAll("(?i)or", ")or(");		//?i: case insensitive match
		
		int i = 1;
		for (String name : map.keySet()) {
			query = query.replaceAll(name, "p" + i + ".value IS NULL OR p" + i + ".value ");
			i++;
		}
		
		return query;
	}
	
	
	private List<ServiceDynamicParameters> sort(List<ServiceDynamicParameters> list, String query) {
		int size = list.size();
		long l[] = new long[size];
		List<ServiceDynamicParameters> sortedList = new LinkedList<ServiceDynamicParameters>();
		
		int w = 0;
		for (ServiceDynamicParameters parameters : list) {
			l[w] = new DistanceFunction(query, parameters).parse();
			w++;
		}
        
		int index[] = new int[size];
		for (int i = 0; i < size; i++) {
			index[i] = i;
		}
        
		for (int i = 0; i < size; i++) {
			long max = l[i];
			int k = i;
			for (int j = i + 1; j < size; j++) {
				if (l[j] > max) {
					max = l[j];
					k = j;
				}
			}
			if (i != k) {
				int tmp = index[i];
				index[i] = index[k];
				index[k] = tmp;
				
				long tmp2 = l[i];
				l[i] = l[k];
				l[k] = tmp2;
			}
		}
        
		for (int i = 0; i < size; i++) {
			ServiceDynamicParameters parameters = list.get(index[i]);
			parameters.setDistanceValue(l[i]);
			sortedList.add(parameters);
		}
        
		return sortedList;
	}


	public synchronized Double getMaxValue(final String parameter) {
		return (Double) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(final Session session)
					throws SQLException {
				SQLQuery sqlQuery = session.createSQLQuery("SELECT MAX(value) FROM parameterValue p WHERE p.name = '" + parameter + "'");
				return Double.parseDouble(sqlQuery.uniqueResult().toString());
			}
		});
	}

	public synchronized Double getMinValue(final String parameter) {
		return (Double) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(final Session session)
					throws SQLException {
				SQLQuery sqlQuery = session.createSQLQuery("SELECT MIN(value) FROM parameterValue p WHERE p.name = '" + parameter + "'");
				return Double.parseDouble(sqlQuery.uniqueResult().toString());
			}
		});
	}

}
